Skip to content

Add aria-describedby attribute where missing for fields with error#2579

Open
AbdiTolesa wants to merge 55 commits into
masterfrom
issue-6068-fix_checkbox_and_radio_buttons_accessibility
Open

Add aria-describedby attribute where missing for fields with error#2579
AbdiTolesa wants to merge 55 commits into
masterfrom
issue-6068-fix_checkbox_and_radio_buttons_accessibility

Conversation

@AbdiTolesa
Copy link
Copy Markdown
Contributor

@AbdiTolesa AbdiTolesa commented Nov 10, 2025

Fix https://github.com/Strategy11/formidable-pro/issues/6078

  • Add missing aria-describedby attributes when field errors happen.
  • Make sure aria-describedby values are removed from elements when errors are cleared, during Ajax submission
  1. Create a form and add Checkbox, Radio and Dropdown fields to it.
  2. Set constraints like Required in the fields options.
  3. Preview the forms and try submitting the form without filling the fields so that errors are triggered.
  4. Confirm that aria-describedby attribute is added to the elements to link them to the error message element programatically.
  5. Repeat the test under different form settings, like JS validation, Ajax submission etc.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 10, 2025

Walkthrough

The changes enhance ARIA accessibility for form fields by making the add_aria_description method public and propagating field args context throughout field templates and controllers. Additionally, JavaScript components for dashboard and overlay are refactored from monolithic to modular webpack-bundled architecture, and error handling in JavaScript is updated to apply ARIA attributes per input.

Changes

Cohort / File(s) Summary
ARIA Accessibility & Field Context - PHP
classes/controllers/FrmFieldsController.php, classes/models/fields/FrmFieldType.php, classes/models/FrmFieldFormHtml.php
FrmFieldsController now loads field type objects to call add_aria_description() when field args are present. FrmFieldType changes method visibility from protected to public with updated docblock. FrmFieldFormHtml refines control flow for alert role injection/removal with explicit early-exit handling.
Field Data Propagation to Templates
classes/views/frm-fields/front-end/checkbox-field.php, classes/views/frm-fields/front-end/dropdown-field.php, classes/views/frm-fields/front-end/radio-field.php
Each template now assigns $field['args'] = $args before the frm_field_input_html hook invocation, propagating runtime arguments to field context. PHPDoc annotations added for variable documentation.
Error Handling & ARIA Updates - JavaScript
js/formidable.js
Introduces per-input ARIA handling for error elements. Adds updateInputElementsAriaDescribedBy() helper to manage aria-describedby attributes. Error placement and aria-invalid now apply across all inputs within a field container rather than single inputs.
Dashboard Component Modernization
js/formidable_dashboard.js
Replaced monolithic minified script with modular webpack-bundled architecture. Introduces frmCounter, frmTabsNavigator classes and utility modules (animation, async, error, URL, validation, visibility helpers) with consolidated public API re-exports.
Overlay Component Refactoring
js/formidable_overlay.js
Converted from inline IIFE to class-based frmOverlay component with webpack modular dependencies. Initializes global instance via window.frmOverlay = new frmOverlay(). Modular exports for constants and utility functions.
CSS Updates
css/admin/frm-settings-components.css, css/frm_testing_mode.css
frm-settings-components.css contains apparent duplication. frm_testing_mode.css introduces stray closing comment marker and duplicated CSS blocks, potentially creating syntax issues.

Sequence Diagram(s)

sequenceDiagram
    participant Ctrl as FrmFieldsController<br/>(input_html)
    participant FT as FrmFieldType
    participant Hook as Hook Caller<br/>(frm_field_input_html)
    
    rect rgb(200, 220, 255)
    Note over Ctrl,Hook: ARIA Description Flow
    Ctrl->>Ctrl: Check if field['args] set
    alt args present
        Ctrl->>FT: Load field type object
        Ctrl->>FT: add_aria_description(args, &$html)<br/>(public method)
        FT->>FT: Inject ARIA attributes into $html
        FT-->>Ctrl: Return modified $html
    else no args
        Ctrl->>Ctrl: Use base $html
    end
    end
    
    Ctrl->>Ctrl: Assign $html (unset $add_html)
    Ctrl->>Hook: echo $html or return $html
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested labels

action: needs qa

Suggested reviewers

  • Crabcyborg
  • lauramekaj1

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.51% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding aria-describedby attributes for fields with errors, which is the primary objective across all modified files.
Description check ✅ Passed The description is directly related to the changeset, explaining the fix for issue #6078, outlining the implementation details (aria-describedby for Checkbox, Radio, Dropdown fields), and providing manual test steps.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e652e4 and 966aabe.

📒 Files selected for processing (4)
  • css/admin/frm-settings-components.css
  • css/frm_admin.css
  • css/frm_testing_mode.css
  • js/formidable.js
✅ Files skipped from review due to trivial changes (1)
  • css/admin/frm-settings-components.css
🚧 Files skipped from review as they are similar to previous changes (1)
  • js/formidable.js
🧰 Additional context used
🪛 Biome (2.1.2)
css/frm_testing_mode.css

[error] 3-3: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.

text-align is already defined here.

Remove or rename the duplicate property to ensure consistent styling.

(lint/suspicious/noDuplicateProperties)


[error] 3-3: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.

min-width is already defined here.

Remove or rename the duplicate property to ensure consistent styling.

(lint/suspicious/noDuplicateProperties)


[error] 3-3: Duplicate properties can lead to unexpected behavior and may override previous declarations unintentionally.

border-radius is already defined here.

Remove or rename the duplicate property to ensure consistent styling.

(lint/suspicious/noDuplicateProperties)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 8 tests in WP 6.9
  • GitHub Check: PHP 7.4 tests in WP 6.9
  • GitHub Check: Cypress
🔇 Additional comments (1)
css/frm_testing_mode.css (1)

3-3: No actionable issues found in this file.

The source SCSS (resources/scss/test-mode/frm_testing_mode.scss) is well-structured with appropriate imports from component files. The minified CSS output correctly compiles these imports together. Properties like text-align and border-radius appear legitimately in different selectors with different values—this is normal CSS, not duplication. No duplicate property declarations or merge conflict artifacts were detected.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca9cb12 and 603d9b1.

📒 Files selected for processing (2)
  • classes/views/frm-fields/front-end/dropdown-field.php (1 hunks)
  • classes/views/frm-fields/front-end/radio-field.php (1 hunks)
🧰 Additional context used
🪛 GitHub Check: PHPStan
classes/views/frm-fields/front-end/radio-field.php

[failure] 57-57:
Implicit array creation is not allowed - variable $field might not exist.

🔇 Additional comments (1)
classes/views/frm-fields/front-end/radio-field.php (1)

57-58: <!-- [scratchpad_start]
[task review_file_1/1 verify_args_availability 3/3 COMPLETE]
Final verification complete - $args is in scope

[observations]

  • Found FrmFieldType::include_on_front_form() at lines 1071-1098
  • Template file included directly via: include $include_file; (line 1093)
  • Variables available in template scope:
    • $args (passed as parameter at line 1071, not removed)
    • $field (assigned at line 1085 from $this->field)
    • $html_id (assigned at line 1086)
    • $field_name (assigned at line 1087)
    • $read_only (assigned at line 1088)
  • NO extract() call - variables made available via direct assignment
  • Before include, only $args['form'] is unset (line 1090)

[analysis]

  • $args parameter is passed through to include_on_front_form()
  • All variables needed by template are in scope before include
  • Radio-field.php line 57: $field['args'] = $args; is VALID
  • $args is definitely in scope when template executes
  • No undefined variable error can occur

[conclusion]

  • Review comment concern is INVALID
  • The code is CORRECT
  • $args is proven to be in scope
  • No changes needed
  • All similar files (checkbox-field.php, dropdown-field.php) follow same pattern

[done]
Verification complete: NO ISSUES FOUND

No issues found. The $args variable is properly in scope.

The template file is included at line 1093 of FrmFieldType::include_on_front_form() with $args passed as a parameter and available in the local scope. Additional variables like $field, $html_id, and $field_name are assigned before the include statement, making them available to the template. The assignment $field['args'] = $args; at lines 57-58 in radio-field.php is valid and safe.

Comment thread classes/views/frm-fields/front-end/dropdown-field.php
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 603d9b1 and 60c34ef.

📒 Files selected for processing (1)
  • js/formidable.js (1 hunks)

Comment thread js/formidable.js Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 60c34ef and 39cf3ed.

📒 Files selected for processing (3)
  • classes/controllers/FrmFieldsController.php (1 hunks)
  • classes/models/fields/FrmFieldType.php (1 hunks)
  • classes/views/frm-fields/front-end/checkbox-field.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • classes/views/frm-fields/front-end/checkbox-field.php
  • classes/models/fields/FrmFieldType.php
🧰 Additional context used
🧬 Code graph analysis (1)
classes/controllers/FrmFieldsController.php (2)
classes/factories/FrmFieldFactory.php (2)
  • FrmFieldFactory (9-141)
  • get_field_object (58-64)
classes/models/fields/FrmFieldType.php (1)
  • add_aria_description (1362-1410)
🔇 Additional comments (1)
classes/controllers/FrmFieldsController.php (1)

527-527: Refactoring approach looks good.

The introduction of the $html variable to store the constructed HTML before output is a clean approach that allows the ARIA description to be conditionally added before echoing or returning. This separation of HTML construction and output handling makes the code more maintainable.

Also applies to: 534-538

@lauramekaj1
Copy link
Copy Markdown
Contributor

Hi @AbdiTolesa, there are some error warnings showing in this branch. Please see the attached screenshot:
image

@AbdiTolesa
Copy link
Copy Markdown
Contributor Author

Hi @AbdiTolesa, there are some error warnings showing in this branch. Please see the attached screenshot:

Hi @lauramekaj1 Just fixed those.

Copy link
Copy Markdown
Contributor

@lauramekaj1 lauramekaj1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbdiTolesa I tested it and it's working as expected. Thank you!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
classes/views/frm-fields/front-end/checkbox-field.php (1)

76-79: Passing $args via $field['args'] into the hook is consistent and safe

Conditionally attaching $args onto $field before frm_field_input_html gives hook callbacks the extra context they need (e.g., for ARIA handling) without changing the hook signature or breaking existing consumers. The isset( $args ) guard avoids notices when $args is not provided, and reusing the mutated $field later in this template remains compatible with current usage.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2058760 and b6faf96.

📒 Files selected for processing (5)
  • classes/controllers/FrmFieldsController.php (1 hunks)
  • classes/models/FrmFieldFormHtml.php (1 hunks)
  • classes/views/frm-fields/front-end/checkbox-field.php (2 hunks)
  • classes/views/frm-fields/front-end/dropdown-field.php (1 hunks)
  • classes/views/frm-fields/front-end/radio-field.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • classes/views/frm-fields/front-end/radio-field.php
  • classes/views/frm-fields/front-end/dropdown-field.php
  • classes/controllers/FrmFieldsController.php
🧰 Additional context used
🧬 Code graph analysis (1)
classes/models/FrmFieldFormHtml.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-4666)
  • should_include_alert_role_on_field_errors (3684-3686)
🔇 Additional comments (1)
classes/views/frm-fields/front-end/checkbox-field.php (1)

11-16: Docblock variable annotations are clear and accurate

The added @var annotations correctly document the template variables and their types, improving readability and IDE support without affecting runtime behavior.

Comment thread classes/models/FrmFieldFormHtml.php
@AbdiTolesa AbdiTolesa requested a review from truongwp November 20, 2025 11:39
Comment thread classes/controllers/FrmFieldsController.php Outdated
Comment thread classes/models/FrmFieldFormHtml.php
@AbdiTolesa AbdiTolesa requested a review from truongwp November 21, 2025 10:58
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec5d850 and 4482d15.

📒 Files selected for processing (2)
  • classes/controllers/FrmFieldsController.php (1 hunks)
  • classes/models/FrmFieldFormHtml.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • classes/controllers/FrmFieldsController.php
🧰 Additional context used
🧬 Code graph analysis (1)
classes/models/FrmFieldFormHtml.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-4666)
  • should_include_alert_role_on_field_errors (3684-3686)
🪛 GitHub Check: PHPStan
classes/models/FrmFieldFormHtml.php

[failure] 236-236:
Variable $error in empty() always exists and is always falsy.

🔇 Additional comments (1)
classes/models/FrmFieldFormHtml.php (1)

239-249: LGTM! Role injection/removal logic is correct.

The logic correctly handles role="alert" injection and removal:

  • Line 240: $has_alert_role correctly indicates presence (fixed from previous inverted logic)
  • Line 241: Injects role when missing AND setting allows
  • Line 247: Removes role when present AND setting disallows

The early return at line 235 ensures this block only executes when an error exists, addressing truongwp's concern from the previous review.

Comment thread classes/models/FrmFieldFormHtml.php
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
classes/models/FrmFieldFormHtml.php (1)

235-237: Consider separating the function call from the return statement.

The code returns the result of FrmShortcodeHelper::remove_inline_conditions(), but this function modifies $this->html by reference and doesn't return a meaningful value (as seen on line 251 where it's called without capturing the return). While functionally correct, explicitly separating the call from the return improves clarity.

Apply this diff for clearer intent:

 if ( empty( $error ) ) {
-	return FrmShortcodeHelper::remove_inline_conditions( false, 'error', $error, $this->html );
+	FrmShortcodeHelper::remove_inline_conditions( false, 'error', $error, $this->html );
+	return;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4482d15 and 2b43dd8.

📒 Files selected for processing (1)
  • classes/models/FrmFieldFormHtml.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
classes/models/FrmFieldFormHtml.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-4666)
  • should_include_alert_role_on_field_errors (3684-3686)
🔇 Additional comments (1)
classes/models/FrmFieldFormHtml.php (1)

239-251: LGTM! Alert role logic is now correct.

The inverted logic bug has been successfully resolved. The variable $has_alert_role now correctly reflects whether role="alert" is present in the HTML (false !== strpos(...) evaluates to true when found). The conditional branches properly inject the role when missing and enabled, or remove it when present but disabled.

@AbdiTolesa AbdiTolesa removed the request for review from truongwp November 27, 2025 13:32
@AbdiTolesa AbdiTolesa requested a review from Crabcyborg December 4, 2025 20:43
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
js/formidable.js (1)

1148-1186: Bug: aria-describedby is computed but never applied to inputs in addFieldError

Inside addFieldError, the inputs.forEach loop builds a describedBy string for each input but never calls setAttribute, so aria-describedby is not actually updated for any input. This means your JS/AJAX validation path will still fail to expose errors to assistive tech, undermining the main goal of this PR.

You can fix this by making describedBy local to the loop and writing it back to the element:

-	function addFieldError( $fieldCont, key, jsErrors ) {
-		let id, describedBy, roleString;
+	function addFieldError( $fieldCont, key, jsErrors ) {
+		let id, roleString;
@@
-		container.classList.add( 'frm_blank_field' );
-		const inputs = container.querySelectorAll( 'input, select, textarea' );
-		id = getErrorElementId( key, inputs[ 0 ] );
+		container.classList.add( 'frm_blank_field' );
+		const inputs = container.querySelectorAll( 'input, select, textarea' );
+		id = getErrorElementId( key, inputs[ 0 ] );
@@
-			container.insertAdjacentHTML( 'beforeend', errorHtml );
-			inputs.forEach( input => {
-				describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
-				if ( ! describedBy ) {
-					describedBy = id;
-				} else if ( describedBy.indexOf( id ) === -1 && describedBy.indexOf( 'frm_error_field_' ) === -1 ) {
-					const errorFirst = input.dataset.errorFirst;
-					if ( errorFirst === '0' ) {
-						describedBy = describedBy + ' ' + id;
-					} else {
-						describedBy = id + ' ' + describedBy;
-					}
-				}
-			} );
+			container.insertAdjacentHTML( 'beforeend', errorHtml );
+			inputs.forEach( input => {
+				if ( ! input ) {
+					return;
+				}
+				let describedBy = input.getAttribute( 'aria-describedby' );
+				if ( ! describedBy ) {
+					describedBy = id;
+				} else if ( describedBy.indexOf( id ) === -1 && describedBy.indexOf( 'frm_error_field_' ) === -1 ) {
+					const errorFirst = input.dataset.errorFirst;
+					if ( errorFirst === '0' ) {
+						describedBy = describedBy + ' ' + id;
+					} else {
+						describedBy = id + ' ' + describedBy;
+					}
+				}
+				input.setAttribute( 'aria-describedby', describedBy );
+			} );

This preserves your existing logic (including data-error-first ordering) while actually wiring up the attribute on every input in the group.

♻️ Duplicate comments (1)
js/formidable.js (1)

1260-1279: Harden updateInputElementsAriaDescribedBy against missing IDs

If an error element doesn’t have an id, el.id is an empty string and the selector [aria-describedby*=""] will match every element with an aria-describedby attribute. The current filter means you won’t remove anything, but you’ll still iterate over all such elements unnecessarily, and this repeats the earlier “missing null check” concern that was already raised on this PR.

A small guard keeps this efficient and future‑proof:

-	function updateInputElementsAriaDescribedBy( el ) {
-		document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
+	function updateInputElementsAriaDescribedBy( el ) {
+		if ( ! el || ! el.id ) {
+			return;
+		}
+		document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
 			let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
 			ariaDescribedBy = ariaDescribedBy.filter( value => value.trim() && value !== el.id );

(You can keep the rest of the function as is; the token filtering already avoids substring false positives.)

🧹 Nitpick comments (1)
js/formidable.js (1)

1281-1289: Consider also clearing aria-invalid when removing all errors

removeAllErrors now correctly cleans up aria-describedby via updateInputElementsAriaDescribedBy, but it doesn’t reset aria-invalid. After an AJAX submit where some fields were previously invalid, those fields can keep aria-invalid="true" even when no .frm_error remains, which is misleading for assistive tech.

You could optionally normalize this here:

 	function removeAllErrors() {
 		document.querySelectorAll( '.form-field' ).forEach( field => {
 			field.classList.remove( 'frm_blank_field', 'has-error' );
 		} );
+		document.querySelectorAll( '.form-field [aria-invalid="true"]' ).forEach( el => {
+			el.setAttribute( 'aria-invalid', 'false' );
+		} );
 		document.querySelectorAll( '.form-field .frm_error' ).forEach( el => {
 			updateInputElementsAriaDescribedBy( el );
 			el.remove();
 		} );

Also, the new JSDoc above updateInputElementsAriaDescribedBy still uses @since x.x; updating that to the actual release version when known will keep the docs consistent with the rest of the file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b1c15c and d6bf75e.

📒 Files selected for processing (1)
  • js/formidable.js (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: Cypress

@Crabcyborg
Copy link
Copy Markdown
Contributor

@AbdiTolesa I'm still not seeing aria-describedby when I have AJAX submit enabled.

@Crabcyborg Crabcyborg modified the milestones: 6.26, 6.27 Dec 8, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
js/formidable.js (2)

1170-1183: Questionable logic in error ID existence check.

Line 1174 checks if describedBy.indexOf( 'frm_error_field_' ) === -1, which prevents adding the current error ID if any error field ID pattern is already present. This could cause issues when errors are updated but not fully cleared first, as the new error ID won't be added if an old error ID remains.

Consider checking only for the specific error ID (id) rather than the generic pattern, or explicitly replace any existing error IDs with the new one.

Example scenario: If field has aria-describedby="frm_error_field_123 field_description" and a new error with ID frm_error_field_456 is added, the new ID won't be added due to this check.


1222-1259: Inconsistent multi-input handling in removeFieldError.

While addFieldError now correctly handles multiple inputs (lines 1170-1183 and 1186-1195), removeFieldError still only processes the first input found by querySelector on line 1230. This creates an inconsistency for checkbox/radio groups when using JS validation:

  1. When an error is added, all inputs in the group get aria-describedby
  2. When the error is cleared, only the first input has aria-describedby cleaned up
  3. Other inputs in the group retain references to removed error elements

This directly relates to the reported gap: "inputs did not show aria-describedby when JS validation is enabled."

Consider updating removeFieldError to handle multiple inputs similar to addFieldError:

 	const errorMessage = container.querySelector( '.frm_error' );
 	const errorId = errorMessage ? errorMessage.id : '';
-	const input = container.querySelector( 'input, select, textarea' );
-	let describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
+	const inputs = container.querySelectorAll( 'input, select, textarea' );

 	container.classList.remove( 'frm_blank_field', 'has-error' );

-	if ( input ) {
-		if ( 'true' === input.getAttribute( 'aria-invalid' ) ) {
-			input.setAttribute( 'aria-invalid', 'false' );
-		} else if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
-			const group = input.closest( '[role="radiogroup"], [role="group"]' );
-			if ( group ) {
-				group.setAttribute( 'aria-invalid', 'false' );
-			}
-		}
-	}
+	inputs.forEach( input => {
+		if ( 'true' === input.getAttribute( 'aria-invalid' ) ) {
+			input.setAttribute( 'aria-invalid', 'false' );
+		} else if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
+			const group = input.closest( '[role="radiogroup"], [role="group"]' );
+			if ( group ) {
+				group.setAttribute( 'aria-invalid', 'false' );
+			}
+		}
+	} );

 	if ( errorMessage ) {
 		errorMessage.remove();
 	}

-	if ( input ) {
-		input.removeAttribute( 'aria-describedby' );
-		if ( describedBy ) {
-			describedBy = describedBy.replace( errorId, '' ).trim();
-			if ( describedBy ) {
-				input.setAttribute( 'aria-describedby', describedBy );
-			}
-		}
-	}
+	inputs.forEach( input => {
+		let describedBy = input.getAttribute( 'aria-describedby' );
+		if ( describedBy ) {
+			describedBy = describedBy.replace( errorId, '' ).trim();
+			if ( describedBy ) {
+				input.setAttribute( 'aria-describedby', describedBy );
+			} else {
+				input.removeAttribute( 'aria-describedby' );
+			}
+		}
+	} );
 }
🧹 Nitpick comments (1)
js/formidable.js (1)

1261-1280: Well-implemented ARIA cleanup with minor safety suggestion.

The helper correctly removes the specific error ID from aria-describedby while preserving other IDs (like field descriptions), and handles multiple inputs properly.

For maximum safety, consider a null check on line 1271:

 	document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
-		let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
+		let ariaDescribedBy = ( input.getAttribute( 'aria-describedby' ) || '' ).split( ' ' );
 		ariaDescribedBy = ariaDescribedBy.filter( value => value.trim() && value !== el.id );

This prevents potential null reference errors if the attribute is removed between query and access, though the selector makes this scenario unlikely.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6bf75e and c55910d.

⛔ Files ignored due to path filters (2)
  • js/formidable_admin.js.map is excluded by !**/*.map, !**/*.map
  • js/formidable_blocks.js.map is excluded by !**/*.map, !**/*.map
📒 Files selected for processing (1)
  • js/formidable.js (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Cypress
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: PHP 7.4 tests in WP trunk
🔇 Additional comments (4)
js/formidable.js (4)

1156-1157: LGTM: Multi-input collection for better accessibility.

The change correctly switches from handling a single input to collecting all inputs within the field container, which properly supports checkbox and radio groups for ARIA attribute management.


1186-1195: LGTM: Proper ARIA invalid state handling.

The implementation correctly applies aria-invalid to radio/checkbox groups (on the parent container with role) versus individual inputs, following ARIA best practices.


1207-1213: LGTM: ESLint spacing issues resolved.

The bracket spacing now correctly matches the file's style guide with spaces inside brackets ([ 0 ]), and the ID generation logic is consistent across both branches.


1286-1289: LGTM: Proper error cleanup with ARIA synchronization.

The updated removeAllErrors correctly calls the new helper to clean up aria-describedby attributes before removing error elements, ensuring screen readers get updated state. The more specific selector (.frm_form_field) is also appropriate.

@AbdiTolesa AbdiTolesa requested review from Crabcyborg and removed request for Crabcyborg December 9, 2025 11:41
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
classes/models/fields/FrmFieldType.php (1)

1560-1620: Public visibility change is reasonable, but please verify child overrides and finalize the @since version.

Making add_aria_description public is a sensible way to reuse the ARIA logic from FrmFieldsController::input_html, and the method body remains unchanged, so behavior is consistent.

Two follow‑ups:

  1. Check for subclasses with narrower visibility.
    If any class extending FrmFieldType overrides add_aria_description as protected, this will now conflict with the parent’s public visibility and cause a fatal error when the class is loaded. Please scan the codebase for overrides and update them to public if they exist.

  2. Replace the placeholder version in the docblock.
    @since x.x Function privacy changed from \protected` to `public`.` should use the actual release version before merging.

You can use this script to look for overrides:

#!/bin/bash
# Find all implementations of add_aria_description in the plugin.
rg -nP 'function\s+add_aria_description\s*\('
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c55910d and c728c84.

📒 Files selected for processing (1)
  • classes/models/fields/FrmFieldType.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
classes/models/fields/FrmFieldType.php (1)
classes/controllers/FrmFieldsController.php (1)
  • input_html (531-563)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Cypress
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
classes/models/fields/FrmFieldType.php (1)

1543-1543: Replace version placeholder in @SInCE tag.

The @since x.x placeholder should be replaced with the actual version number for this release.

js/formidable.js (2)

1156-1157: Add safety check for empty inputs array.

If a field container has no inputs (edge case), accessing inputs[0] on line 1157 will return undefined, potentially causing issues in getErrorElementId().

🔎 Proposed fix
 	container.classList.add( 'frm_blank_field' );
 	const inputs = container.querySelectorAll( 'input, select, textarea' );
+	if ( ! inputs.length ) {
+		return;
+	}
 	id = getErrorElementId( key, inputs[ 0 ] );

1269-1280: Add validation for error element ID.

If el.id is empty or undefined, the selector on line 1270 will be malformed (e.g., [aria-describedby*=""]), potentially matching unintended elements.

🔎 Proposed fix
 function updateInputElementsAriaDescribedBy( el ) {
+	if ( ! el.id ) {
+		return;
+	}
 	document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
 		let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
 		ariaDescribedBy = ariaDescribedBy.filter( value => value.trim() && value !== el.id );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c728c84 and 6feac4f.

📒 Files selected for processing (4)
  • classes/controllers/FrmFieldsController.php
  • classes/models/FrmFieldFormHtml.php
  • classes/models/fields/FrmFieldType.php
  • js/formidable.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • classes/controllers/FrmFieldsController.php
🧰 Additional context used
🧬 Code graph analysis (2)
classes/models/FrmFieldFormHtml.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-5054)
  • should_include_alert_role_on_field_errors (4013-4015)
classes/models/fields/FrmFieldType.php (1)
classes/controllers/FrmFieldsController.php (1)
  • input_html (531-563)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 7.4 tests in WP 6.9
  • GitHub Check: PHP 8 tests in WP 6.9
  • GitHub Check: Cypress
🔇 Additional comments (1)
classes/models/FrmFieldFormHtml.php (1)

289-310: LGTM! Error shortcode handling is correct.

The refactored logic properly handles three cases:

  1. Early exit when no error exists (lines 289-292)
  2. Conditional injection of role="alert" when enabled and missing (lines 298-304)
  3. Removal of role="alert" when disabled but present (lines 305-306)

The variable naming and logic flow are now correct and address all concerns from previous reviews.

Comment thread js/formidable.js
return 'frm_error_field_' + key.split( '-' )[ 0 ];
}
return 'frm_error_' + input.id;
return 'frm_error_' + input.id.split( '-' )[ 0 ];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbdiTolesa I think we want to still include part of this array.

If there is a repeater, the error IDs are something like abepx-0. So we need to only remove part of the ID and not everything right of the first dash.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we only need to skip splitting the field id in the block above this line as repeating fields check goes in there and I have done that. I believe that addresses your concern.

Comment thread js/formidable.js Outdated
@Crabcyborg Crabcyborg modified the milestones: 6.27, 6.28 Jan 5, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
js/formidable.js (1)

1222-1259: Inconsistent multi-input handling breaks accessibility for radio/checkbox groups.

The removeFieldError function only updates the first input's aria-describedby attribute, while addFieldError correctly handles all inputs. This creates an accessibility issue:

Scenario: During JS validation, when a radio/checkbox group error is cleared (user fixes the validation issue), the error message is removed but aria-describedby attributes remain on all inputs except the first one. These orphaned attributes point to a non-existent error element, breaking screen reader announcements.

Consider refactoring to use the new updateInputElementsAriaDescribedBy helper for consistent multi-input handling:

🔎 Proposed fix using the new helper function
 function removeFieldError( fieldCont ) {
 	const container = fieldCont instanceof jQuery ? fieldCont.get( 0 ) : fieldCont;
 	if ( ! container ) {
 		return;
 	}
 
 	const errorMessage = container.querySelector( '.frm_error' );
-	const errorId = errorMessage ? errorMessage.id : '';
 	const input = container.querySelector( 'input, select, textarea' );
-	let describedBy = input ? input.getAttribute( 'aria-describedby' ) : null;
 
 	container.classList.remove( 'frm_blank_field', 'has-error' );
 
 	if ( input ) {
 		if ( 'true' === input.getAttribute( 'aria-invalid' ) ) {
 			input.setAttribute( 'aria-invalid', 'false' );
 		} else if ( [ 'radio', 'checkbox' ].includes( input.type ) ) {
 			const group = input.closest( '[role="radiogroup"], [role="group"]' );
 			if ( group ) {
 				group.setAttribute( 'aria-invalid', 'false' );
 			}
 		}
 	}
 
 	if ( errorMessage ) {
+		updateInputElementsAriaDescribedBy( errorMessage );
 		errorMessage.remove();
 	}
-
-	if ( input ) {
-		input.removeAttribute( 'aria-describedby' );
-		if ( describedBy ) {
-			describedBy = describedBy.replace( errorId, '' ).trim();
-			if ( describedBy ) {
-				input.setAttribute( 'aria-describedby', describedBy );
-			}
-		}
-	}
 }
🤖 Fix all issues with AI Agents
In @js/formidable_dashboard.js:
- Around line 888-893: The code assumes querySelector found elements and adds an
event listener without guards; add null checks for userEmailInput and
subscribeButton before calling subscribeButton.addEventListener to avoid
TypeError, e.g. only attach the listener if both userEmailInput and
subscribeButton are non-null, and keep calling
_this.saveSubscribedEmail(userEmailInput.value) inside the handler (or use an
arrow function to preserve lexical this if needed); ensure the handler
references the same unique symbols userEmailInput, subscribeButton, and
saveSubscribedEmail so the listener is only registered when the elements exist.
- Around line 122-124: The beforeunload handler is passing this.cleanupObservers
directly so its this will be window when invoked; update the addEventListener
call to preserve the instance context by binding the method (e.g.,
window.addEventListener('beforeunload', this.cleanupObservers.bind(this))) or by
wrapping it in an arrow function that calls this.cleanupObservers(), and ensure
any corresponding removeEventListener uses the same bound function reference
(store the bound function on the instance, e.g., this._boundCleanupObservers) so
cleanupObservers can access this.resizeObserver correctly.

In @js/formidable.js:
- Around line 1261-1283: ESLint space-in-parens violation: remove extraneous
spaces inside parentheses in updateInputElementsAriaDescribedBy; e.g. change
calls and callbacks like document.querySelectorAll( `[aria-describedby*="${
el.id }"]` ), input.getAttribute( 'aria-describedby' ).split( ' ' ), the
.filter( value => { ... } ) callback, and input.setAttribute(
'aria-describedby', ariaDescribedBy.join( ' ' ) ) to use no inner spaces (e.g.
`[aria-describedby*="${el.id}"]`, getAttribute('aria-describedby'), .split(' '),
.filter(value => { ... }), setAttribute('aria-describedby',
ariaDescribedBy.join(' '))). Ensure all parentheses in
updateInputElementsAriaDescribedBy follow the project's space-in-parens rule.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6feac4f and 4e652e4.

⛔ Files ignored due to path filters (8)
  • js/addons-page.js.map is excluded by !**/*.map, !**/*.map
  • js/form-templates.js.map is excluded by !**/*.map, !**/*.map
  • js/formidable_admin.js.map is excluded by !**/*.map, !**/*.map
  • js/formidable_blocks.js.map is excluded by !**/*.map, !**/*.map
  • js/formidable_dashboard.js.map is excluded by !**/*.map, !**/*.map
  • js/formidable_overlay.js.map is excluded by !**/*.map, !**/*.map
  • js/formidable_styles.js.map is excluded by !**/*.map, !**/*.map
  • js/onboarding-wizard.js.map is excluded by !**/*.map, !**/*.map
📒 Files selected for processing (15)
  • css/admin/frm-settings-components.css
  • css/admin/welcome-tour.css
  • css/frm_testing_mode.css
  • js/addons-page.js
  • js/form-templates.js
  • js/formidable-settings-components.js
  • js/formidable.js
  • js/formidable_admin.js
  • js/formidable_blocks.js
  • js/formidable_dashboard.js
  • js/formidable_overlay.js
  • js/formidable_styles.js
  • js/frm_testing_mode.js
  • js/onboarding-wizard.js
  • js/welcome-tour.js
✅ Files skipped from review due to trivial changes (2)
  • css/frm_testing_mode.css
  • css/admin/frm-settings-components.css
🧰 Additional context used
🧬 Code graph analysis (1)
js/formidable_dashboard.js (3)
js/src/core/utils/animation.js (1)
  • frmAnimate (1-76)
js/src/core/utils/error.js (1)
  • span (26-26)
js/src/core/utils/globalModules.js (1)
  • bold (6-6)
🪛 Biome (2.1.2)
js/formidable_overlay.js

[error] 16-16: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 224-224: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 527-527: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)

js/formidable_dashboard.js

[error] 15-15: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 83-83: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 285-285: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 588-588: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 848-848: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)

🪛 GitHub Actions: Inspections
js/formidable.js

[error] 1275-1275: eslint: There must be a space before this paren (space-in-parens) at /home/runner/work/formidable-forms/formidable-forms/js/formidable.js:1275:5

🪛 GitHub Check: Run ESLint
js/formidable.js

[failure] 1275-1275:
There must be a space before this paren

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Cypress
  • GitHub Check: PHP 8 tests in WP 6.9
  • GitHub Check: PHP 7.4 tests in WP 6.9
🔇 Additional comments (5)
css/admin/welcome-tour.css (1)

1-152: Verify whether this file should be in this PR.

This CSS file is unrelated to the PR's objective of adding aria-describedby attributes to form fields (Checkbox, Radio, Dropdown). The actual accessibility changes are in FrmFieldType.php, FrmFieldFormHtml.php, radio.js, and related field classes. Since the commit message indicates "Fix merge conflicts," this CSS file may have been accidentally included during merge resolution and should likely be removed.

[scope_verification]

js/formidable_overlay.js (1)

1-788: Build output file - static analysis warnings are false positives.

This is a webpack-bundled output file. The Biome warnings about function reassignment (lines 16, 224, 527) are false positives caused by Babel's standard transpilation pattern for helper functions like _typeof. These self-reassigning helpers are intentional and safe.

If source files are available for review, those would be preferable for code review rather than the compiled bundle.

js/formidable_dashboard.js (1)

860-958: Dashboard initialization pattern is well-structured overall.

The frmDashboard class demonstrates good practices in initCloseWelcomeBanner (lines 906-918) with proper null checks before adding event listeners. Consider applying the same defensive pattern to initInbox.

js/formidable.js (2)

1156-1198: LGTM: Multi-input support correctly implemented.

The updated addFieldError function now properly handles fields with multiple inputs (radio buttons, checkboxes). By iterating over all inputs to set aria-describedby and aria-invalid, screen readers will correctly associate error messages with each input in a group.


1285-1294: LGTM: Proper ARIA cleanup for AJAX submissions.

The removeAllErrors function now correctly removes error IDs from all inputs' aria-describedby attributes by calling updateInputElementsAriaDescribedBy before removing each error element. This ensures accessibility attributes are properly cleaned up during AJAX form submissions.

Comment on lines +122 to +124
// Cleanup observers when page unloads to prevent memory leaks
window.addEventListener('beforeunload', this.cleanupObservers);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

this context lost in beforeunload event handler.

"bind causes a fixed this context to be assigned" - when this.cleanupObservers is passed directly to addEventListener, the this context will be window when invoked, not the frmTabsNavigator instance. The cleanupObservers method references this.resizeObserver (line 187), which will be undefined in the wrong context.

🔎 Proposed fix using bind
-window.addEventListener('beforeunload', this.cleanupObservers);
+window.addEventListener('beforeunload', this.cleanupObservers.bind(this));

Or use an arrow function wrapper:

-window.addEventListener('beforeunload', this.cleanupObservers);
+window.addEventListener('beforeunload', () => this.cleanupObservers());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Cleanup observers when page unloads to prevent memory leaks
window.addEventListener('beforeunload', this.cleanupObservers);
}
// Cleanup observers when page unloads to prevent memory leaks
window.addEventListener('beforeunload', () => this.cleanupObservers());
}
🤖 Prompt for AI Agents
In @js/formidable_dashboard.js around lines 122 - 124, The beforeunload handler
is passing this.cleanupObservers directly so its this will be window when
invoked; update the addEventListener call to preserve the instance context by
binding the method (e.g., window.addEventListener('beforeunload',
this.cleanupObservers.bind(this))) or by wrapping it in an arrow function that
calls this.cleanupObservers(), and ensure any corresponding removeEventListener
uses the same bound function reference (store the bound function on the
instance, e.g., this._boundCleanupObservers) so cleanupObservers can access
this.resizeObserver correctly.

Comment on lines +888 to +893
var userEmailInput = document.querySelector('#frm_leave_email');
var subscribeButton = document.querySelector('#frm-add-my-email-address');
subscribeButton.addEventListener('click', function () {
_this.saveSubscribedEmail(userEmailInput.value).then();
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing null checks before adding event listeners.

this refers to the element when a method is passed as a callback, but more critically, subscribeButton and userEmailInput are not checked for null before use. If these elements don't exist in the DOM, addEventListener will throw a TypeError.

🔎 Proposed fix with null guards
 var userEmailInput = document.querySelector('#frm_leave_email');
 var subscribeButton = document.querySelector('#frm-add-my-email-address');
+if (!subscribeButton || !userEmailInput) {
+  return;
+}
 subscribeButton.addEventListener('click', function () {
   _this.saveSubscribedEmail(userEmailInput.value).then();
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var userEmailInput = document.querySelector('#frm_leave_email');
var subscribeButton = document.querySelector('#frm-add-my-email-address');
subscribeButton.addEventListener('click', function () {
_this.saveSubscribedEmail(userEmailInput.value).then();
});
}
var userEmailInput = document.querySelector('#frm_leave_email');
var subscribeButton = document.querySelector('#frm-add-my-email-address');
if (!subscribeButton || !userEmailInput) {
return;
}
subscribeButton.addEventListener('click', function () {
_this.saveSubscribedEmail(userEmailInput.value).then();
});
🤖 Prompt for AI Agents
In @js/formidable_dashboard.js around lines 888 - 893, The code assumes
querySelector found elements and adds an event listener without guards; add null
checks for userEmailInput and subscribeButton before calling
subscribeButton.addEventListener to avoid TypeError, e.g. only attach the
listener if both userEmailInput and subscribeButton are non-null, and keep
calling _this.saveSubscribedEmail(userEmailInput.value) inside the handler (or
use an arrow function to preserve lexical this if needed); ensure the handler
references the same unique symbols userEmailInput, subscribeButton, and
saveSubscribedEmail so the listener is only registered when the elements exist.

Comment thread js/formidable.js
Comment on lines +1261 to +1283
/**
* Updates the aria-describedby attribute of input elements to remove the error element ID.
*
* @since x.x
*
* @param {HTMLElement} el The error element.
* @return {void}
*/
function updateInputElementsAriaDescribedBy( el ) {
document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
ariaDescribedBy = ariaDescribedBy.filter( value => {
const trimmedValue = value.trim();
return trimmedValue && trimmedValue !== el.id;
});

if ( ariaDescribedBy.length ) {
input.setAttribute( 'aria-describedby', ariaDescribedBy.join( ' ' ) );
return;
}
input.removeAttribute( 'aria-describedby' );
} );
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix ESLint spacing error blocking CI.

The updateInputElementsAriaDescribedBy helper function correctly handles removing error IDs from all inputs' aria-describedby attributes. However, there's a spacing violation on line 1275 preventing the build from passing.

🔎 Fix for ESLint space-in-parens violation
 	function updateInputElementsAriaDescribedBy( el ) {
 		document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
 			let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
 			ariaDescribedBy = ariaDescribedBy.filter( value => {
 				const trimmedValue = value.trim();
 				return trimmedValue && trimmedValue !== el.id;
-			});
+			} );
 
 			if ( ariaDescribedBy.length ) {
 				input.setAttribute( 'aria-describedby', ariaDescribedBy.join( ' ' ) );
 				return;
 			}
 			input.removeAttribute( 'aria-describedby' );
 		} );
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Updates the aria-describedby attribute of input elements to remove the error element ID.
*
* @since x.x
*
* @param {HTMLElement} el The error element.
* @return {void}
*/
function updateInputElementsAriaDescribedBy( el ) {
document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
ariaDescribedBy = ariaDescribedBy.filter( value => {
const trimmedValue = value.trim();
return trimmedValue && trimmedValue !== el.id;
});
if ( ariaDescribedBy.length ) {
input.setAttribute( 'aria-describedby', ariaDescribedBy.join( ' ' ) );
return;
}
input.removeAttribute( 'aria-describedby' );
} );
}
/**
* Updates the aria-describedby attribute of input elements to remove the error element ID.
*
* @since x.x
*
* @param {HTMLElement} el The error element.
* @return {void}
*/
function updateInputElementsAriaDescribedBy( el ) {
document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ).forEach( input => {
let ariaDescribedBy = input.getAttribute( 'aria-describedby' ).split( ' ' );
ariaDescribedBy = ariaDescribedBy.filter( value => {
const trimmedValue = value.trim();
return trimmedValue && trimmedValue !== el.id;
} );
if ( ariaDescribedBy.length ) {
input.setAttribute( 'aria-describedby', ariaDescribedBy.join( ' ' ) );
return;
}
input.removeAttribute( 'aria-describedby' );
} );
}
🧰 Tools
🪛 GitHub Actions: Inspections

[error] 1275-1275: eslint: There must be a space before this paren (space-in-parens) at /home/runner/work/formidable-forms/formidable-forms/js/formidable.js:1275:5

🪛 GitHub Check: Run ESLint

[failure] 1275-1275:
There must be a space before this paren

🤖 Prompt for AI Agents
In @js/formidable.js around lines 1261 - 1283, ESLint space-in-parens violation:
remove extraneous spaces inside parentheses in
updateInputElementsAriaDescribedBy; e.g. change calls and callbacks like
document.querySelectorAll( `[aria-describedby*="${ el.id }"]` ),
input.getAttribute( 'aria-describedby' ).split( ' ' ), the .filter( value => {
... } ) callback, and input.setAttribute( 'aria-describedby',
ariaDescribedBy.join( ' ' ) ) to use no inner spaces (e.g.
`[aria-describedby*="${el.id}"]`, getAttribute('aria-describedby'), .split(' '),
.filter(value => { ... }), setAttribute('aria-describedby',
ariaDescribedBy.join(' '))). Ensure all parentheses in
updateInputElementsAriaDescribedBy follow the project's space-in-parens rule.

@Crabcyborg Crabcyborg modified the milestones: 6.28, 6.29 Feb 12, 2026
@Crabcyborg Crabcyborg modified the milestones: 6.29, 6.30 Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants